#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <unistd.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>/*fd_set types*/

#include <fcntl.h>/*for fctrl makes sockets not blocked*/
#include <string.h> /*for memset*/
#include <assert.h>

#include "genserver.h"
#include "BTree.h"
#include "List.h"


#define   BAKCLOG         (5)
#define   INOUT
#define   OUT
#define   DEADBEAF        (void*)0xdeadbeaf

struct mess2serv
{
    size_t          m_size;
    char*           buf;
};


typedef struct Client_t
{
    int             m_fd;
    int             m_isAlldone;/*The status is all action
                                 protected client from possible drops are completed
                                 At this version it can't be removed without finishig all writing*/
    int             m_isdorp;/*Is Cliet has to be dropped*/
    int             m_isheadrd;/*Flag is header was readed*/
    size_t          m_readed;
    size_t          m_rest2read;
    char*           m_readbuf;
    size_t          m_oobreaded;
    size_t          m_oobrest2read;
    char*           m_oobbuf;/*Buffer for oob messages*/
    size_t          m_writed;
    size_t          m_rest2write;
    char*           m_writebuf;
    List*           m_messageQ;/*List of messages structs with
                                    messages from Server size and buf*/
} Client;

struct GenericServer_t
{
    int             m_port;
    int             m_servSockfd;
    int             m_isStop;
    void*           m_appl;
    size_t          m_headSize;
    size_t          m_exmesSize;

    int             m_backlog;
    HeaderFunc      m_headF;

    NewClientCb     m_newClF;
    NewMesssageCb   m_newMesF;
    ClientDiscnCb   m_clDiscF;
    NewExeptionCb   m_newExepF;

    Tree*           m_clientT;
};
/****************List**of**Internal Funcitons*******************************/
/*Initillize server socket: create, set up different socket options,
 bind it and  insert it to listening
 Returns servSockfd or -1 on fails*/
static int  GsvInit(int _port);

static int  GsvSocketNew(int _port);
static void GsvBind(int _socket,int _port);
/*returns 0 on success and 1 on error*/
static int  GsvListentoClient(int _socket);
static void GsvSetSockNonBlock(int _socket);

/*Reset the fd_set before each select loop
Insert the existed file descriptors into fd_set
Returns the MaxFileDescriptor + 1 used as 1st argument of select funciton*/
static int GsvBuildSelectList(GenSv* _this, fd_set* _fd_rd,

                                fd_set* _fd_wr,fd_set* _fd_ex);

/*If o.k returns 0 else returns error log*/
static int  GsvUseReadySocket(GenSv* _this,fd_set* _fd_rd,
                              fd_set* _fd_wr, fd_set* _fd_ex);

/*Get the fd of new client, allocate struct client, send the Callback to application
Returns 0 if success and error log (err_Gsv) if fails*/
static err_Gsv GsvAcceptNewClient(GenSv* _this,
                            OUT TreeIter* _newClientIter, OUT Client** _newclient);
/*returns null pointer if error*/
static Client* GsvNewClient(int _newclientfd);
/*Iterator on newClient*/
static TreeIter GsvAddNewClientandCB(GenSv*_this,Client* _newclient);
/*Deallocat all allocated resourses by client and deallocate it self after*/
static void GsvclientDestroy(Client* _Client);
/*returns fd of new connection*/
static int MakeNewConnection(int _servSocket);



int GsvClientComp(void* _oldClient,void* _newClient)
{
    if(((Client*)_oldClient)->m_fd >= ((Client*)_newClient)->m_fd);
    {
        return 0;
    }
    return 1;
}

/*Initillizing of struct passed to function*/
GenSv*  GsvServerNew(int _port, void* _appdata,int _headersize,
        HeaderFunc _headerFunc,
        NewClientCb _newClient,NewMesssageCb _newMessage,
        ClientDiscnCb _clientDiscn,
        size_t _exptMessagSize,NewExeptionCb _newExeption)
{
    GenSv*  sv;

    assert(_headersize);
    assert(_headerFunc);
    assert(_newClient);
    assert(_newMessage);
    assert(_clientDiscn);
    assert(_exptMessagSize);
    assert(_newExeption);

    sv = (GenSv*)malloc(sizeof(GenSv));
    if (sv == 0)
    {
        fprintf(stderr,"\nAllocation Server Failed line : %d \n",__LINE__);
        return 0;
    }

    sv->m_clientT =  TreeNew(GsvClientComp);
    if (sv->m_clientT == 0)
    {
        fprintf(stderr,"\nAllocation Client Tree Failed line : %d \n",__LINE__);
        return 0;
    }

/*    sv->m_MessQu2Serv =(List*)ListNew();
    if (sv->m_MessQu2Serv == 0)
    {
        fprintf(stderr,"\nAllocation of Message List  Failed line : %d \n",__LINE__);
        return 0;
    }*/

    sv->m_port      = _port ;
    sv->m_appl      = _appdata;
    sv->m_headSize  = _headersize;
    sv->m_exmesSize = _exptMessagSize;
    sv->m_backlog   = BAKCLOG;
    sv->m_isStop    = 0;

    sv->m_headF     = _headerFunc;
    sv->m_newClF    = _newClient;
    sv->m_newMesF   = _newMessage;
    sv->m_clDiscF   = _clientDiscn;
    sv->m_newExepF  = _newExeption;
    return sv;
}

int GsvServerRun(GenSv* _this)
{
    fd_set                  fd_rd;
    fd_set                  fd_wr;
    fd_set                  fd_ex;
    int                     maxfdsize;
    int                     numwaikedfd;
    int                     error = 0;
    struct timeval          wait;

     /*set up the timout for select*/

    _this->m_servSockfd = GsvInit(_this->m_port);

    /*Stars the select loop*/
    while(1)
    {
       wait.tv_sec  = 3;
       wait.tv_usec = 0;
       maxfdsize = GsvBuildSelectList(_this,&fd_rd,&fd_wr,&fd_ex);
       numwaikedfd = select( maxfdsize,/*1st param - max number socket to check system define FD_SETSIZE ==1024*/
                            &(fd_rd),&(fd_wr),
                            &fd_ex,/*OUT_OF_BAND data transaction*/
                            &wait);/*waiting interwal :
                                   0 pointer- blocked indefinitely
                                    timeval == 0 - imidiatly (pooling)*/

        if ( numwaikedfd == -1)
        {
            perror("select");
            close(_this->m_servSockfd);
            exit(EXIT_FAILURE);
        }
        if ( numwaikedfd == 0)
        {
            /*nothing to use condition*/
            putchar('.');
            fflush(stdout);
        }

        else
        {
           error = GsvUseReadySocket(_this,&fd_rd,&fd_wr,&fd_ex);
        }

        if(_this->m_isStop)
        {
            break;
        }
    }
    return error;
}


static int GsvInit(_port)
{
    int     servSockfd;

   /*producing the server socket*/
    servSockfd = GsvSocketNew(_port);
/*     close(servSockfd);*/

    GsvSetSockNonBlock(servSockfd);
    /*Insert the the serverSocket in to listen state*/
    if (GsvListentoClient(servSockfd))
    {
        return -1;
    }
    return servSockfd;
}

static int GsvSocketNew(int _port)
{
    int                     servSockfd;

    int      re_bind = 1;

    /*1.Obtain socket for listenign server socket*/
    servSockfd = socket( AF_INET /*1st argument is the purpose AF_UNIX local*/ ,
                SOCK_STREAM /*communication sematics SOCK_STREAM ==two way connect*/,
                IPPROTO_TCP/*Protocol TCP  0 use the default it is also TCP*/);

    if(servSockfd ==-1)
    {
        perror("socket");
        close(servSockfd);
        exit(EXIT_FAILURE);
        return 1;
    }

  /*2.Define of some socket options*/

    setsockopt(servSockfd,/*Will used for server*/
    SOL_SOCKET , /* Level of API for manipulation ( if we want to define somthing on TCP level the Protocol number will  be provided TCP*/
    SO_REUSEADDR,/* opt_name == SO_REUSEADDR this optins allows to to re-bind */
    (void*) &(re_bind),/*optval == 1 answer Yes or true for the opt_name (argument above)*/
     sizeof(re_bind)/*has to be sizeof(int) i don't undersand its usage*/
    );
    /*Binding*/
    GsvBind(servSockfd,_port);
    return servSockfd;
}

static void GsvBind(int _socket, int _port )
{
    struct sockaddr_in      serv_addr;

    /*Initiallizing of socket address struct*/
    memset((char *) &serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;/*Domain declaration , internet*/
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);/*converts number representation from  byte order to netorder*/
    serv_addr.sin_port =htons(_port);

    if( bind(_socket,(struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1 )
    {
        perror("bind");
        close(_socket);
        exit(EXIT_FAILURE);
    }
}

static void GsvSetSockNonBlock(int _socket)
{
    int     blkstat;
    /*to obtainet the current status*/
    blkstat = fcntl(_socket,F_GETFL);
    if (blkstat < 0)
    {
        perror("fcntl(F_GETFL)");
        close(_socket);
        return;
    }

    /*  SetSockNonBlock sets flag == O_NONBLOCK. this flag makes setting of socket to be
    non_blocking in all fucntions. In most of all
    funcitons instead of blocking routine : the errno will be set to EAGAIN.
     In "connect function" errno will obtains EINPROGRESS,
    the primary goal of this order to make the "accept" non blocking  */
    if ( fcntl(_socket,F_SETFL,blkstat| O_NONBLOCK) < 0)
    {
        perror("fcntl(F_SETFL)");
        close(_socket);
        return;
    }
    return;
}


static int  GsvListentoClient(int _socket)
{
    if( listen(_socket,BAKCLOG) )/*BACKLOG -is the lenght of clients queue on listening*/
    {
        perror("listen");
        return 1;
    }
    return 0;
}

/*
static void GsvSocketListReset(int _servSockfd,fd_set* _fd_rd,
                                fd_set* _fd_wr,fd_set* _fd_ex)
{
    FD_ZERO(_fd_rd);/*CLears or initialliziong of fd list
    FD_ZERO(_fd_wr);
    FD_ZERO(_fd_ex);
    FD_SET(_servSockfd,_fd_rd);adds the _socket to the list of sockest for select, when the connection
                                to thes socket is arrived the select will not remove it from appropriate
                                fd_set.
                                By egreement the servSocket
                                will be with readed FD
    return;
}*/



static int GsvBuildSelectList(GenSv* _this, fd_set* _fd_rd,
                                fd_set* _fd_wr,fd_set* _fd_ex)
{
    TreeIter        clientItr = TreeBegin(_this->m_clientT) ;
    TreeIter        clientItrEnd = TreeEnd(_this->m_clientT);
    Client*         client;
    int             maxfd;

    FD_ZERO(_fd_rd);/*CLears or initialliziong of fd list*/
    FD_ZERO(_fd_wr);
    FD_ZERO(_fd_ex);
    FD_SET(_this->m_servSockfd,_fd_rd);/*adds the _socket to the list of sockest for select, when the connection
                                to thes socket is arrived the select will not remove it from appropriate
                                fd_set.
                                By egreement the servSocket
                                will be with readed FD*/

    /*insert the file descriptors to fd_set*/
    while( !(TreeIsEqual(_this->m_clientT,clientItr,clientItrEnd) == 1) )
    {
        client =(Client*)TreeGetData(clientItr);
        FD_SET(client->m_fd,_fd_rd);
        FD_SET(client->m_fd,_fd_wr);
        FD_SET(client->m_fd,_fd_ex);
        clientItr =  TreeNext(_this->m_clientT,clientItr);
    }

    /*Get the maximal filed descriptor the 1st argument of select function*/
    if ( TreeIsEmpty(_this->m_clientT) )
    {
        maxfd = _this->m_servSockfd+1;
    }
    else
    {
       client = (Client*)TreeGetData ( TreePrev(_this->m_clientT,clientItrEnd) );
       maxfd  =client->m_fd +1;
    }
    return maxfd;
}

/*If o.k returns 0 else returns error log*/
static int GsvUseReadySocket(GenSv* _this,fd_set* _fd_rd, fd_set* _fd_wr, fd_set* _fd_ex)
{
/* Algorithm
   1.If new socket accept
        Get new fd
        Create its struct
        Insert to tree
        Send the Callback

  2.If writing request
        write

  3.If OOB request
        if isDrop == yes don't enter to OOB routine
        OOB reading action

  4.If reading request
        iif isDrop == yes don't enter  not enters to read routine
        reading action
        if isDrop == yes and isAlldone == yes use Drop function

  5. Drop function send CallBack (ClientDiscnCb) to application removes Client
  from Tree
*/
    int         connectfd=0;
    int         listnum=0;
    err_Gsv     err;
    Client*     newclient;
    TreeIter    newclientIter;

 /*Determind  is exist request for new connection*/
     if(FD_ISSET(_this->m_servSockfd,_fd_rd))
     {
         if ( (err = GsvAcceptNewClient(_this,OUT &newclientIter,OUT &newclient )) )
         {
             return err;
         }
     }
 return 0;
}
/*         if (newclient->m_isdorp == 1 && newclient->m_isAlldone ==1)
         {
             GsvDropClients(_this,clientIter);
         }*/

        /*Add fd to exeption list*
        for(listnum = 0; listnum < MAXSOCK2CHECK ; ++listnum)
        {
            if(_exeptlist[listnum]==0)
            {
                _exeptlist[listnum] = connectfd;
                break;
            }
        }
      ReadTheData(connectfd);
     }

    /*Determind the exption descriptors*
    for(listnum = 0; listnum < MAXSOCK2CHECK ; ++listnum)
    {
        if(FD_ISSET(_exeptlist[listnum],_fd_ep))
        {
           printf("\nFD disconnect %d \n",_readlist[listnum]);
/*TODO function that removed the FD descriptor from write read lists*
        }
    }
    /*Determind the ready descriptors*

    for(listnum = 0; listnum < MAXSOCK2CHECK ; ++listnum)
    {
        if(FD_ISSET(_readlist[listnum],_fd_rd))
        {
            ReadTheData(_readlist[listnum]);
        }

         if(FD_ISSET(_writlist[listnum],_fd_wr))
         {
             WriteTheData(_writlist[listnum]);
         }
    }

    return 0;
}
*/

static err_Gsv GsvAcceptNewClient(GenSv* _this,
                            OUT TreeIter* _newClientIter, OUT Client** _newclient)
{
    int         connectfd=0;

    connectfd = MakeNewConnection(_this->m_servSockfd);
    if (connectfd == -1)
    {
        printf("\n Connection failed \n");
        return GSV_NEW_CLIENT_CONNECTION_PROBLEM;
    }
        /*      Create its struct*/

    if ( (*_newclient = GsvNewClient(connectfd)) == 0 )
    {
        fprintf(stderr,"\nNEW_CLIENT_CREATION_PROBLEMS\n");
             return GSV_NEW_CLIENT_CREATION_PROBLEMS;
    }
    /*Insert New Client to to tree
        Send the Callback*/
   *_newClientIter = GsvAddNewClientandCB(_this,*_newclient);
   if ( *_newClientIter == TreeEnd(_this->m_clientT) )
   {
       GsvclientDestroy(*_newclient);
       *_newclient = DEADBEAF;
       fprintf(stderr,"\nGSV_NEW_CLIENT_INSERTION_PROBLEMS\n");
        return GSV_NEW_CLIENT_INSERTION_PROBLEMS;
   }
   return 0;
}

static int MakeNewConnection(int _servSocket)
{
    /*fd only and returns it*/

    int                     statconnect = 0;
    int                     l;
    struct sockaddr_in      saddr;

    l= sizeof(saddr);/*This is INOUT PARAMETER used for verification is the right
                                        struct type is accepted*/
    statconnect = accept(_servSocket,(struct sockaddr*)&saddr, (socklen_t*)&l);
    if (statconnect == -1)
    {
        perror("accept");
        return -1;
    }
#ifdef _DEBUGSV
    fprintf(stdout,"connection esteblished from %s\n" , inet_ntoa(saddr.sin_addr));
#endif /*_DEBUGSV*/
    GsvSetSockNonBlock(statconnect);
    return statconnect;
}

static Client* GsvNewClient(int _newclientfd)
{
    /*1. Create the client struct*/

    Client*    newclient = (Client*)malloc(sizeof(Client));
    if (newclient == 0)
    {
        fprintf(stderr,"\n malloc new client fails, line :%d \n",__LINE__);
        return 0;
    }

    newclient->m_messageQ =ListNew();
    if (newclient->m_messageQ == 0)
    {
        free(newclient);
        newclient = 0;
        fprintf(stderr,"\n malloc new client message Q fails, line :%d \n",__LINE__);
        return 0;
    }


    newclient->m_fd           = _newclientfd;
    newclient->m_isAlldone    = 1;/*All done no any request to perform*/
    newclient->m_isdorp       = 0;

    newclient->m_isheadrd     = 0;
    newclient->m_readed       = 0;
    newclient->m_rest2read    = 0;
    newclient->m_readbuf      = 0;

    newclient->m_oobreaded    = 0;
    newclient->m_oobrest2read = 0;
    newclient->m_oobbuf       = 0;

    newclient->m_writed       = 0;
    newclient->m_rest2write   = 0;
    newclient->m_writebuf     = 0;

    return newclient;
}

static TreeIter GsvAddNewClientandCB(GenSv*_this,Client* _newclient)
{
    /*  1. Insert to Tree
        2. Turns to Callback
      */
    TreeIter   newclientItr = TreeInsert(_this-> m_clientT,(void*)_newclient);
    _this->m_newClF(_newclient->m_fd,_this->m_appl);
    return newclientItr;
}

static void GsvclientDestroy(Client* _Client)
{
    if (_Client-> m_readbuf)
    {
        free(_Client-> m_readbuf);
    }

    if (_Client-> m_writebuf)
    {
        free(_Client->m_writebuf);
    }

    listDestroy(_Client->m_messageQ);
    free(_Client);
}

/*Not finished*
 static void GsvDropClients(List* _dropList)
 {
    ListIter_t        clientItr    = ListBegin(_dropList) ;
    ListIter_t        clientItrEnd = ListEnd(_dropList);
    Client*           client;

    while( (TreeIsEqual(_clientT,clientItr,clientItrEnd) == 1) )
    {
        client =(Client*)TreeGetData(clientItr);
        FD_SET(client->m_fd,fd_rd);
        FD_SET(client->m_fd,fd_wr);
        FD_SET(client->m_fd,fd_ep);
        clientItr =  TreeNext(_clientT,clientItr);
    }
 }*/

